const axios = require('axios')
const crypto = require('crypto')
const { Certificate } = require('@fidm/x509')
const logger = require('./logger')
const AUTH_TYPE = "WECHATPAY2-SHA256-RSA2048"
const CERTIFICATES = []
const wechatPayAPI = axios.create({
  baseURL: "https://api.mch.weixin.qq.com",
  headers: {
    "Accept": "application/json",
    "Content-Type": "application/json"
  }
})

/**
 * 获取证书序列号
 */
function getSerial_no(publicKey) {
  return Certificate.fromPEM(publicKey).serialNumber
}



/**
 * 微信支付
 * 文档地址:https://pay.weixin.qq.com/docs/merchant/apis/native-payment/direct-jsons/native-prepay.html
 */
class WechatPay {
  constructor({ appid, mchid, publicKey, privateKey, secretKey }) {
    this.appid = appid
    this.mchid = mchid
    this.publicKey = publicKey
    this.serial_no = getSerial_no(publicKey)
    this.privateKey = privateKey
    this.secretKey = secretKey
    logger.info("this.secretKey", this.secretKey)
  }
  /** 构建签名串 */
  sign(method, url, timestamp, nonceStr, body) {
    let requestSignStr = `${method}\n${url}\n${timestamp}\n${nonceStr}\n`
    requestSignStr += method === "GET" ? '\n' : `${JSON.stringify(body)}\n`
    // 进行SHA256签名, 并进行Base64编码
    const rsaSha = crypto.createSign('RSA-SHA256')
    rsaSha.update(requestSignStr)
    return rsaSha.sign(this.privateKey, 'base64')
  }
  /**
   * 往微信支付发送请求
   */
  async request(method, url, body = {}) {
    // 1.请求时间戳
    const timestamp = Math.floor(Date.now() / 1000).toString()
    // 2.请求随机串
    const nonceStr = Math.random().toString(36).substring(2, 17)
    // 3.构建签名串
    const requestSignStr = this.sign(method, url, timestamp, nonceStr, body)
    const headers = {
      Authorization: `${AUTH_TYPE} mchid="${this.mchid}",nonce_str="${nonceStr}",signature="${requestSignStr}",timestamp="${timestamp}",serial_no="${this.serial_no}"`
    }

    const response = await wechatPayAPI.request({
      method,
      url,
      data: body,
      headers
    })
    return response.data
  }

  decrypt(encrypted) {
    //algorithm=AEAD_AES_256_GCM
    //ciphertext=加密后的证书内容,nonce加密证书的随机串】 对应到加密算法中的IV。
    //加密算法中的IV就是加盐，即使原文一样，密钥一样，因为盐值的不同，密文也不一样
    const { ciphertext, associated_data, nonce } = encrypted;
    const encryptedBuffer = Buffer.from(ciphertext, 'base64');
    //encryptedBuffer分成二部分，最后的16个字节是认证标签
    const authTag = encryptedBuffer.subarray(encryptedBuffer.length - 16);
    //前面的才是加密后的内容
    //AEAD_AES_256_GCM 提供了认证加密的功能，在这个模块式，除了加密的数据本身外，还生成一个认证标签的额外数据
    //用于保证数据的完整性的真实性
    //AAD附加认证数据 AAD是在加密过程中使用的数据，但不会被加密
    const encryptedData = encryptedBuffer.subarray(0, encryptedBuffer.length - 16);
    //创建一个解密器
    const decipher = crypto.createDecipheriv('aes-256-gcm', this.secretKey, nonce);
    decipher.setAuthTag(authTag);//设置认证标签
    decipher.setAAD(Buffer.from(associated_data));//设置附加认证数据
    //开始解密，得到解密结果 
    const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
    const decryptedString = decrypted.toString('utf8');
    return decryptedString
  }

  /**
   * 获取微信平台公钥
   * @param serial_no 微信支付平台证书的序列号
   */
  async getWechatPublicKey(serial) {
    //先尝试从缓存中读取微信的公钥
    const wechatPayPublicKey = CERTIFICATES[serial];
    //如果有则直接返回此公钥
    if (wechatPayPublicKey) return wechatPayPublicKey;
    //获取商户当前可用的微信支付平台证书列表
    const url = '/v3/certificates';
    const result = await this.request('GET', url);
    logger.info('certificates.result', result);
    //获取证书列表
    const certificates = result.data;
    certificates.forEach(({ serial_no, encrypt_certificate }) => {
      //解密证书
      const certificate = this.decrypt(encrypt_certificate);
      logger.info('certificate', certificate);
      //取出解密后的证收中的公钥，转成PEM格式并缓存在CERTIFICATES
      CERTIFICATES[serial_no] = Certificate.fromPEM(certificate).publicKey.toPEM();
    });
    //返回此序列号对应的微信平台公钥
    return CERTIFICATES[serial];
  }
  /**
   * Native 统一下单接口支付
   */
  async transactions_native(params) {
    const url = '/v3/pay/transactions/native'
    const requestParams = {
      appid: this.appid,
      mchid: this.mchid,
      ...params
    }
    return await this.request('POST', url, requestParams)
  }
  /**
   * 验证签名
   * @param {body,timestamp,nonce,signature,serial} 微信支付平台的应答报文主体 微信支付平台的应答时间戳 微信支付平台的应答随机串 微信支付平台的应答签名 微信支付平台证书的序列号
   */
  async verifySign(params) {
    //构造验签名串
    const { body, signature, serial, nonce, timestamp } = params;
    const wechatPayPublicKey = await this.getWechatPublicKey(serial);
    logger.info('wechatPayPublicKey', wechatPayPublicKey);
    const verifySignStr = `${timestamp}\n${nonce}\n${JSON.stringify(body)}\n`;
    logger.info('verifySignStr', verifySignStr);
    //获取应答签名,使用 base64 解码 Wechatpay-Signature 字段值，得到应答签名。
    //最后，验证签名，得到验签结果。
    const verify = crypto.createVerify('RSA-SHA256')//创建验证对象
    verify.update(verifySignStr);//更新验证数据
    //微信平台的公钥,签名，编码
    return verify.verify(wechatPayPublicKey, signature, 'base64');
  }
  /** 商户订单查询订单 */
  async query(params) {
    const { out_trade_no } = params
    const url = `/v3/pay/transactions/out-trade-no/${out_trade_no}?mchid=${this.mchid}`;
    return await this.request('GET', url);
  }
  /** 关闭订单 */
  async close({ out_trade_no }) {
    const url = `/v3/pay/transactions/out-trade-no/${out_trade_no}/close`
    await this.request('POST', url, {
      mchid: this.mchid
    })
  }
}

module.exports = WechatPay